package com.yanqu.road.connect;

import com.google.protobuf.InvalidProtocolBufferException;
import com.yanqu.road.AgentServer;
import com.yanqu.road.pb.activity.ActivityProto;
import com.yanqu.road.pb.kuang.Kuang;
import com.yanqu.road.pb.syncMessage.SyncMessage;
import com.yanqu.road.pb.union.UnionActivityGroupProto;
import com.yanqu.road.utils.ReconnectUtils;
import com.yanqu.road.dao.impl.servercenter.CrossServerInfoDaoImpl;
import com.yanqu.road.entity.servercenter.CrossRankServerInfo;
import com.yanqu.road.entity.servercenter.CrossServerInfo;
import com.yanqu.road.entity.servercenter.ProcessInfo;
import com.yanqu.road.logic.helper.CrossChannelHelper;
import com.yanqu.road.utils.YanQuMessageUtils;
import com.yanqu.road.NettyMgr;
import com.yanqu.road.define.YanQuNettyDefine;
import com.yanqu.road.pb.cross.BaseCrossProto;
import com.yanqu.road.pb.server.LoadProto;
import com.yanqu.road.player.Player;
import com.yanqu.road.player.PlayerControl;
import com.yanqu.road.server.command.Command;
import com.yanqu.road.server.command.CommandControl;
import com.yanqu.road.server.manger.CrossActivityProcessMgr;
import com.yanqu.road.server.manger.ServerListMgr;
import com.yanqu.road.message.YanQuMessage;
import com.yanqu.road.server.protocol.Protocol;
import com.yanqu.road.utils.*;
import com.yanqu.road.utils.config.Config;
import com.yanqu.road.utils.net.ServerNetConfig;
import com.yanqu.road.utils.net.ServerNetConfigHelper;
import io.netty.channel.ChannelHandlerContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class AgentServerLinkControl {

    private static Logger logger = LogManager.getLogger(AgentServerLinkControl.class.getName());
    private static ServerNetConfig agentServerNetConfig;
    private static CrossRankServerInfo crossRankServerInfo;
    private static Map<Integer, Map<Integer, AgentServerConnectionGroup>> gameServerLinkMap;
    private static final Map<Integer, AgentServerConnectionGroup> crossServerLinkMap = new ConcurrentHashMap<>();

    public static boolean init(int serverIndex) {
        try {
            loadAgentServerNetConfig(serverIndex);
            //连接游戏服
            connectGameServer();
            //连接所有跨服
            connectCrossServer();
        } catch (Exception e) {
            logger.error(e);
            return false;
        }

        return true;
    }

    private static void connectGameServer() {
        gameServerLinkMap = new ConcurrentHashMap<>();
        gameServerLinkMap.put(ServerType.SERVER_GAME, new HashMap<>());
        for (Integer serverType : gameServerLinkMap.keySet()) {
            connectServer(serverType);
        }
    }

    private static void connectCrossServer() {
        long serverId = ConfigHelper.getLong("serverId");
        int channelId = ServerListMgr.getChannelId(serverId);
        List<CrossServerInfo> crossServerInfoList = new CrossServerInfoDaoImpl().getAllChannelCrossServerInfo(channelId);
        CrossRankServerInfo crossRankServerInfoTemp = new CrossServerInfoDaoImpl().getCrossRankServerId(serverId);

        checkCrossRankServerInfo(crossRankServerInfoTemp,crossServerInfoList,serverId);
        crossRankServerInfo = crossRankServerInfoTemp;

        if(Config.isDebug()){
            //测试环境省得一直被覆盖配置，默认只连接配置的跨服
            long id = crossRankServerInfoTemp.getCrossRankServerId();
            if(!Config.getConnectCrossId().contains(id)){
                Config.getConnectCrossId().add(id);
            }
            id = crossRankServerInfoTemp.getCrossCenterServerId();
            if(!Config.getConnectCrossId().contains(id)){
                Config.getConnectCrossId().add(id);
            }
            id = crossRankServerInfoTemp.getCrossBanquetServerId();
            if(!Config.getConnectCrossId().contains(id)){
                Config.getConnectCrossId().add(id);
            }
        }

        for (CrossServerInfo crossServerInfo : crossServerInfoList) {
            if (Config.isDebug()){
                if (!Config.getConnectCrossId().isEmpty() && !Config.contains(crossServerInfo.getCrossServerId())){
                    continue;
                }
            }
            for (ProcessInfo info : crossServerInfo.getProcessInfoList()) {
                if (info.getServerName().equals("cross_server")) {
                    if (canConnectCrossServer(crossServerInfo)) {
                        AgentServer.getTimerExecutorService().execute(()->{
                            AgentServerConnectionGroup connectionGroup = connectServer(info);
                            synchronized (crossServerLinkMap) {
                                crossServerLinkMap.put(crossServerInfo.getCrossServerId(), connectionGroup);
                            }
                        });
                    }
                }
            }
        }
    }

    private static  CrossServerInfo findRankServer(int crossRankServerId, List<CrossServerInfo> crossServerInfoList) {
        for (CrossServerInfo serverInfo : crossServerInfoList) {
            if (serverInfo.getCrossServerId() == crossRankServerId && serverInfo.getCrossServerType() != CrossServerType.ACTIVITY_CROSS) {
                return serverInfo;
            }
        }
       return null;
    }

    private static  CrossServerInfo findCrossServer(int crossRankServerId, List<CrossServerInfo> crossServerInfoList) {
        for (CrossServerInfo serverInfo : crossServerInfoList) {
            if (serverInfo.getCrossServerId() == crossRankServerId) {
                return serverInfo;
            }
        }
        return null;
    }


    private static AgentServerConnectionGroup connectServer(ProcessInfo info) {
        int serverId = info.getServerId();
        int serverType = ServerType.SERVER_CROSS_2;
        ServerNetConfig serverNetConfig = new ServerNetConfig(serverId, info.getName(), info.getAddress(), info.getPort(), info.getAdminPort());
        InetSocketAddress address = new InetSocketAddress(serverNetConfig.getAddress(), serverNetConfig.getPort());
        AgentServerConnectionGroup connectionGroup = new AgentServerConnectionGroup(address, null, YanQuNettyDefine.LINK_COUNT_MAX, serverNetConfig, serverType);
        boolean hasFailed = false;
        for (int i = 0; i < YanQuNettyDefine.LINK_COUNT_MAX; i++) {
            try {
                YanQuConnection connection = connectionGroup.connect(i);
                if (connection == null) {
                    logger.error("connect " + serverNetConfig.toString() + " , i = " + i + " fail. ");
                    hasFailed = true;
                    continue;
                }
                connection.getChannel().attr(NettyMgr.LINKED_CLIENT).set(connectionGroup);
                sendRegisterServer(connection, i, serverType);
                connection.setLastHeartTime(System.currentTimeMillis());
            } catch (Exception e) {
                hasFailed = true;
                logger.error("connect " + serverNetConfig.toString() + " , i = " + i + " error. ", e);
            }
        }
        if (hasFailed) {
            ReconnectUtils.getInstance().reconnect(connectionGroup, 10);
        } else {
            logger.error("connect success：" + serverNetConfig.toString());
        }
        return connectionGroup;
    }

    private static void connectServer(int serverType) {
        ServerNetConfig serverNetConfig = ServerNetConfigHelper.getServerNetConfig(serverType, 0);
        if (serverNetConfig == null) {
            logger.error("game server net config no exist");
            return;
        }

        gameServerLinkMap.get(serverType).clear();
        InetSocketAddress address = new InetSocketAddress(serverNetConfig.getAddress(), serverNetConfig.getPort());
        AgentServerConnectionGroup connectionGroup = new AgentServerConnectionGroup(address, null, YanQuNettyDefine.LINK_COUNT_MAX, serverNetConfig, serverType);
        boolean hasFailed = false;
        for (int i = 0; i < YanQuNettyDefine.LINK_COUNT_MAX; i++) {
            try {
                YanQuConnection connection = connectionGroup.connect(i);
                if (connection == null) {
                    logger.error("connect " + serverNetConfig.toString() + " , i = " + i + " fail. ");
                    hasFailed = true;
                    continue;
                }

                connection.getChannel().attr(NettyMgr.LINKED_CLIENT).set(connectionGroup);
                sendRegisterServer(connection, i, serverType);
            } catch (Exception e) {
                hasFailed = true;
                logger.error("connect " + serverNetConfig.toString() + " , i = " + i + " error. ", e);
            }
        }

        gameServerLinkMap.get(serverType).put(serverNetConfig.getServerIndex(), connectionGroup);
        if (hasFailed) {
            ReconnectUtils.getInstance().reconnect(connectionGroup, 10);
        } else {
            logger.info("connect success：" + serverNetConfig.toString());
        }
    }

    private static void loadAgentServerNetConfig(int serverIndex) {
        agentServerNetConfig = ServerNetConfigHelper.getServerNetConfig(ServerType.SERVER_AGENT, serverIndex);
    }


    public static void sendRegisterServer(YanQuConnection connection, int linkIndex, int serverType) {
        short sendCode = 0;
        if (serverType == ServerType.SERVER_GAME) {
            sendCode = Protocol.S_SERVER_REGISTER;
        } else if (serverType == ServerType.SERVER_CROSS || serverType == ServerType.SERVER_CROSS_2 || serverType == ServerType.CENTER_CROSS_2 || serverType == ServerType.BANQUET_CROSS) {
            sendCode = Protocol.C_REGISTER;
            LoadProto.LoadCrossMsg.Builder msg = writeProto(ConfigHelper.getLong("serverId"), agentServerNetConfig, 0, serverType, linkIndex);
            YanQuMessage packet = YanQuMessageUtils.buildMessage(sendCode, msg);
            connection.send(packet);
            return;
        } else {
            return;
        }

        LoadProto.LoadMsg.Builder msg = writeProto(agentServerNetConfig, 0, serverType, linkIndex);
        YanQuMessage packet = YanQuMessageUtils.buildMessage(sendCode, msg);
        connection.send(packet);
    }

    public static void removeServerConn(AgentServerConnectionGroup group) {
    }

    public static void routeActivityCross(YanQuMessage packet) {
        try {
            BaseCrossProto.BaseCrossReqMsg req = BaseCrossProto.BaseCrossReqMsg.parseFrom(packet.getBytes());
            int activityId = req.getActivityId();
            if (activityId <= 0) {
                logger.error("can't find activity cross process. userId:{}, activityId: {}, code: {}", packet.getPlayerId(), activityId, packet.getCode());
                return;
            }
            Integer crossId = CrossActivityProcessMgr.getActivityCrossId(activityId);
            if (crossId == null) {
                logger.error("can't find activity cross process. userId:{}, activityId: {}, code: {}", packet.getPlayerId(), activityId, packet.getCode());
                return;
            }
            //活动原路返回
            if(packet.getCode() == Protocol.C_CROSS_ACTIVITY_RESP_HANDLER){
                try {
                    SyncMessage.Response response = SyncMessage.Response.parseFrom(packet.getBytes());
                    int oldCrossId = crossId;
                    crossId = (int)response.getForwardServerId();
                    if(oldCrossId != crossId) {
                        logger.info("repair activity {} crossId, {} to {},userId:{}", activityId, oldCrossId, crossId, packet.getPlayerId());
                    }
                }catch (Exception e){
                    logger.error("", e);
                }
            }
            AgentServerConnectionGroup group = crossServerLinkMap.get(crossId);
            route(group, packet);
            debugAddCheckCrossId(group, crossId);

        } catch (InvalidProtocolBufferException e) {
            logger.error("parse cross req error. code: {}", packet.getCode());
        }

    }

    private static void debugAddCheckCrossId(AgentServerConnectionGroup group, int crossId) {
        //测试环境
        if(group == null && Config.isDebug()){
            synchronized (Config.getConnectCrossId()){
                long id = (long)crossId;
                if(!Config.getConnectCrossId().contains(id)){
                    Config.getConnectCrossId().add(id);
                }
            }
        }
    }

    public static void routeUnionActivityCross(YanQuMessage packet) {
        int appointCrossId = 0;

        try {
            UnionActivityGroupProto.BaseUnionActivityGroupMsg req = UnionActivityGroupProto.BaseUnionActivityGroupMsg.parseFrom(packet.getBytes());
            int code = req.getCode();

            // 替换协议号
            packet.setCode(code);

            // 找跨服ID
            if (req.hasCrossId()) {
                appointCrossId = req.getCrossId();
            } else {
                int activityId = req.getActivityId();
                int groupId = req.getGroupId();
                int crossId = CrossActivityProcessMgr.getUnionActivityCrossId(activityId, groupId);
                if (crossId < 0) {
                    logger.warn("can't find activty cross process. activtyId: {}, groupId: {}, code: {}", activityId, groupId, code);
                    return;
                }
                appointCrossId = crossId;
            }

        } catch (InvalidProtocolBufferException e) {
            logger.error("执行command失败, message string: {}", packet, e);
            return;
        }

        AgentServerConnectionGroup group = crossServerLinkMap.get(appointCrossId);
        route(group, packet);
        debugAddCheckCrossId(group, appointCrossId);
    }

    /**
     * 游戏服转发到指定跨服
     */
    public static void routeAppointActivityCross(YanQuMessage packet) {
        int appointCrossId = 0;

        try {
            ActivityProto.AgentTransferToAppointCrossMsg req = ActivityProto.AgentTransferToAppointCrossMsg.parseFrom(packet.getBytes());
            int code = req.getCode();
            // 替换协议号
            packet.setCode(code);
            // 跨服ID
            appointCrossId = req.getCrossId();
        } catch (InvalidProtocolBufferException e) {
            logger.error("执行command失败, message string: {}", packet, e);
            return;
        }
        AgentServerConnectionGroup group = crossServerLinkMap.get(appointCrossId);
        route(group, packet);
        debugAddCheckCrossId(group, appointCrossId);
    }

    public static void routeChannelActivityCross(YanQuMessage packet) {
        int crossServerId = (int) CrossChannelHelper.getCross3Id(ConfigHelper.getLong("serverId"));
        AgentServerConnectionGroup group = crossServerLinkMap.get(crossServerId);
        route(group, packet);
        debugAddCheckCrossId(group, crossServerId);
    }

    public static void routeRankCross(YanQuMessage packet) {
        AgentServerConnectionGroup group = crossServerLinkMap.get(crossRankServerInfo.getCrossRankServerId());
        route(group, packet);
    }

    public static void routeBanquetCross(YanQuMessage packet) {
        AgentServerConnectionGroup group = crossServerLinkMap.get(crossRankServerInfo.getCrossBanquetServerId());
        route(group, packet);
    }

    public static void routeCenterCross(YanQuMessage packet) {
        AgentServerConnectionGroup group = crossServerLinkMap.get(crossRankServerInfo.getCrossCenterServerId());
        route(group, packet);
    }

    public static void routeClient(YanQuMessage packet) {
        Player user = PlayerControl.getOnlinePlayer(packet.getPlayerId());
        if (user != null) {
            user.send(packet);
        } else {
            logger.debug("can no find player, userId: " + packet.getPlayerId() + ", code: " + packet.getCode());
        }
    }

    public static void routeGame(YanQuMessage packet) {
        AgentServerConnectionGroup group = gameServerLinkMap.get(ServerType.SERVER_GAME).get(0);
        route(group, packet);
    }

    private static void route(AgentServerConnectionGroup client, YanQuMessage packet) {
        if (client != null) {
            client.send(packet);
        }
    }

    public static void routAgent(ChannelHandlerContext ctx, YanQuMessage yanQuMessage) {
        Command cmd = CommandControl.getCommand(yanQuMessage.getCode());
        if (cmd != null) {
            try {
                cmd.execute(ctx, yanQuMessage);
            } catch (Exception e) {
                logger.error("execute command error, command : {}, message: {}", cmd.toString() ,yanQuMessage, e);
            }
        } else {
            logger.error("Code not find, code {} ", Integer.toHexString(yanQuMessage.getCode()));
        }
    }

    public static LoadProto.LoadCrossMsg.Builder writeProto(long serverId, ServerNetConfig netConfig, int load, int serverType, int linkIndex) {
        LoadProto.LoadCrossMsg.Builder msg = LoadProto.LoadCrossMsg.newBuilder();
        msg.setServerId(serverId);
        msg.setServerIndex(netConfig.getServerIndex());
        msg.setServerName(netConfig.getName() == null ? "" : netConfig.getName());
        msg.setAddress(netConfig.getAddress() == null ? "" : netConfig.getAddress());
        msg.setPort(netConfig.getPort());
        msg.setLoad(load);
        msg.setType(serverType);
        msg.setConnTimes(0);
        msg.setLinkIndex(linkIndex);
        return msg;
    }

    public static LoadProto.LoadMsg.Builder writeProto(ServerNetConfig netConfig, int load, int serverType, int linkIndex) {
        LoadProto.LoadMsg.Builder msg = LoadProto.LoadMsg.newBuilder();
        msg.setServerIndex(netConfig.getServerIndex());
        msg.setServerName(netConfig.getName() == null ? "" : netConfig.getName());
        msg.setAddress(netConfig.getAddress() == null ? "" : netConfig.getAddress());
        msg.setPort(netConfig.getPort());
        msg.setLoad(load);
        msg.setType(serverType);
        msg.setConnTimes(0);
        msg.setLinkIndex(linkIndex);
        return msg;
    }

    public static void checkCrossConnect() {
        for (int serverType : gameServerLinkMap.keySet()) {
            if (serverType == ServerType.SERVER_CROSS || serverType == ServerType.SERVER_CROSS_2 || serverType == ServerType.CENTER_CROSS_2 || serverType == ServerType.BANQUET_CROSS) {
                for (AgentServerConnectionGroup group : gameServerLinkMap.get(serverType).values()) {
                    group.keepAlive();
                }
            }
        }
        for (AgentServerConnectionGroup connectionGroup : crossServerLinkMap.values()) {
            connectionGroup.keepAlive();
        }
    }

    public static void checkNewCrossConnect() {
        logger.info("start check all connect, now connect info: {}", crossServerLinkMap.keySet());
        long serverId = ConfigHelper.getLong("serverId");
        int channelId = ServerListMgr.getChannelId(serverId);
        List<CrossServerInfo> crossServerInfoList = new CrossServerInfoDaoImpl().getAllChannelCrossServerInfo(channelId);
        boolean allConn = true;
        for (CrossServerInfo crossServerInfo : crossServerInfoList) {
            if (Config.isDebug()){
                if (!Config.getConnectCrossId().isEmpty() && !Config.contains(crossServerInfo.getCrossServerId())){
                    continue;
                }
            }
            logger.info("need connect crossServer {}", crossServerInfo.getCrossServerId());
            for (ProcessInfo info : crossServerInfo.getProcessInfoList()) {
                if (info.getServerName().equals("cross_server")) {
                    if (canConnectCrossServer(crossServerInfo)) {
                        if (crossServerLinkMap.containsKey(crossServerInfo.getCrossServerId())) {
                            continue;
                        }
                        logger.error("lost {} connect", crossServerInfo.getCrossServerId());
                        AgentServer.getTimerExecutorService().execute(() -> {
                            AgentServerConnectionGroup connectionGroup = connectServer(info);
                            synchronized (crossServerLinkMap) {
                                crossServerLinkMap.put(crossServerInfo.getCrossServerId(), connectionGroup);
                            }
                        });
                        allConn = false;
                    }
                }
            }
        }
        if (allConn) {
            logger.info("all cross connect!");
        }
    }

    public static void setCrossConnSetCheckTime(int index, int serverType) {
        gameServerLinkMap.get(serverType).get(0).receiveHeartBeat(index);
        logger.info("write connectionTime, ServerType: {} , index : {}", serverType, index);
    }

    public static void setCrossConnSetCheckTime(int index, int serverType, int id) {
        AgentServerConnectionGroup group  = crossServerLinkMap.get(id);
        if (group != null) {
            group.receiveHeartBeat(index);
            if (!Config.isDebug()) {
                logger.info("write connectionTime, ServerType: {} ,id: {}, index : {}", serverType, id, index);
            }
        }
    }

    /**
     * 检查 CrossRankServerInfo配置是否正确
     * @param crossRankServerInfo
     * @param serverId
     */
    public static void checkCrossRankServerInfo(CrossRankServerInfo crossRankServerInfo,List<CrossServerInfo> crossServerInfoList,long serverId){
        if(crossRankServerInfo == null){
            throw new RuntimeException(serverId + " can not find t_s_server_cross_rank_server config");
        }
        // 分组跨服
        if (crossRankServerInfo.getCrossRankServerId() <= 0) {
            throw new RuntimeException(serverId + " can not find crossRankServerId");
        }
        CrossServerInfo crossServerInfo = findCrossServer(crossRankServerInfo.getCrossRankServerId(), crossServerInfoList);
        if (crossServerInfo == null) {
            throw new RuntimeException(serverId + " can not find rankServer");
        }
        if(crossServerInfo.getCrossServerType() != CrossServerType.RANK_CROSS_1){
            throw new RuntimeException(serverId + " rankServer crossServerType is error");
        }
        // 中心跨服
        if (crossRankServerInfo.getCrossCenterServerId() <= 0) {
            throw new RuntimeException(serverId + " can not find crossRankServerId2");
        }
        CrossServerInfo crossServerInfo2 = findCrossServer(crossRankServerInfo.getCrossCenterServerId(), crossServerInfoList);
        if (crossServerInfo2 == null) {
            throw new RuntimeException(serverId + " can not find crossRankServerId2");
        }
        if(crossServerInfo2.getCrossServerType() != CrossServerType.RANK_CROSS_2){
            throw new RuntimeException(serverId + " crossRankServerId2 crossServerType is error");
        }
        // 中心跨服
        if (crossRankServerInfo.getCrossBanquetServerId() <= 0) {
            throw new RuntimeException(serverId + " can not find crossBanquetServerId");
        }
        CrossServerInfo crossBanquetServerInfo = findCrossServer(crossRankServerInfo.getCrossBanquetServerId(), crossServerInfoList);
        if (crossBanquetServerInfo == null) {
            throw new RuntimeException(serverId + " can not find crossBanquetServerId");
        }
        if(crossBanquetServerInfo.getCrossServerType() != CrossServerType.BANQUET_CROSS){
            throw new RuntimeException(serverId + " crossBanquetServerId crossServerType is error");
        }
    }

    /**
     * 判断是否可以连接跨服服务器
     * @param crossServerInfo
     * @return
     */
    public static boolean canConnectCrossServer(CrossServerInfo crossServerInfo) {
        return crossServerInfo.getCrossServerType() == CrossServerType.ACTIVITY_CROSS || crossServerInfo.getCrossServerId() == crossRankServerInfo.getCrossRankServerId() ||
                crossServerInfo.getCrossServerId() == crossRankServerInfo.getCrossCenterServerId() || crossServerInfo.getCrossServerType() == CrossServerType.BANQUET_CROSS;
    }
}
